#!/usr/bin/env python
# encoding: utf-8

import time
import os
import platform
import re
import requests
import json
import configparser
from threading import Thread

import serial
import serial.tools.list_ports
import binascii
localhost_cid = '4E010001'
TYPE = "01"

# 节点超时时间-秒
NODE_ALIVE_TIMEOUT = 30
# 节点保持连接时间-秒
NODE_ALIVE_KEEP = 60

# 节点心跳包
NODE_ALIVE = "B0"
# 节点设备打开阀门
NODE_OPEN_TAP = "B1"
# 节点设备关闭阀门
NODE_CLOSE_TAP = "B2"
# 节点请求连接终端
NODE_REQUEST_CONNECT = "B3"
# 节点设备上线
NODE_ALIVE_ON = "B4"
# 节点设备离线
NODE_ALIVE_OFF = "B5"
# 节点设备不在线
NODE_NOT_ALIVE = "B6"
# 阀门打开状态
NODE_TAP_IS_OPEN = "B7"
# 阀门关闭状态
NODE_TAP_IS_CLOSE = "B8"

# 用户获取所有节点
USER_GET_NODES = "C0"

class Ser:
    def __init__(self):
        self.configfile = os.getcwd()+'/conf/serial.conf'
        self.ser_init()

    def ser_init(self):
        '''初始化串口'''
        self.serial = serial.Serial()
        self.serial.timeout = 0.5# make sure that the alive event can be checked from time to time
        self.serial_alive = False
        self.serial.port = "COM2"
        self.serial.baudrate = 115200
        self.serial.bytesize = 8
        self.serial.parity   = 'N'
        self.serial.stopbits = 1
        # self.serial.dtr      = True
        # self.serial.rts      = True
        self.recv = ''
        self.recv = []
        self.thread_recv = None
        # 串口查询线程
        self.find_open()


    def alive(self):
        pass

    def start_request_connect(self):
        self.connect_alive = True
        self.thread_request_connect= Thread(target=self.request_connect)
        self.thread_request_connect.setDaemon(True)
        self.thread_request_connect.start()

    def request_connect(self):
        """节点请求连接终端
        3s定时发送请求命令到终端
        """
        while self.connect_alive:
            print("请求连接终端中...")
            time.sleep(1)
            if self.connect_alive:
                cmd = self.cmd_send(
                    #  lora_addr = 'ff0012',
                    target = '53010000',
                    _type = '01',
                    cmd = 'B3',
                    content = '4e010001'
                )

    def heartThread(self):
        """心跳包"""
        while self.heart_alive:
            time.sleep(3)
            cmd = self.cmd_send(
                target = '4e010001',
                _type = '01',
                cmd = 'B0',
            )

    def find_open(self):
        """打开串口
        采用configparser读取配置文件：/conf/serial.ini, 从中获取串口设备的硬件ID[serial_number]，
        再根据当前查询到的串口外设[port_list]进行比对，打开满足设定的串口外设
        """
        # 读取默认串口配置文件
        try:
            self.serial.open()
        except serial.SerialException as e:
            print("Open Seial Error ：%s" % e)
        else:
            self.serial_alive = True
            self.thread_recv = Thread(target=self.recv_hander)
            self.thread_recv.setDaemon(True)
            self.thread_recv.start()
            self.start_request_connect()

            # self.thread_write = Thread(target=self.write_hander)
            # self.thread_write.setDaemon(True)
            # self.thread_write.start()
            print("打开串口: %s / %s"%(self.serial.port, self.serial.baudrate))

    def recv_hander(self):
        """串口接收, 串口端口异常情况下，关闭串口，并重新开启串口查询功能"""
        while self.serial_alive:
            try:
                n = self.serial.inWaiting()
                if n:
                    b = self.serial.read(n)
                    # bytes转为16进制字符串
                    s = str(binascii.b2a_hex(b).decode('utf-8')).upper()
                    cmd_list = [c for c in b]
                    self.cmd_deal(s)
            except Exception as e:
                print(e)
                print("串口异常")
                # 关闭串口
                self.serial_alive = False
                self.serial.close()
                print("串口关闭")
                # 重新打开串口查询
                self.thread_find_open = Thread(target=self.find_open)
                self.thread_find_open.setDaemon(True)
                self.thread_find_open.start()
                print("重新查询串口")

    def cmd_deal(self, recv):
        '''串口处理命令，监测校验和是否正确'''
        if recv.startswith('AA'):
            data = recv[4:-2]
            theory_len =  int(recv[2:4], 16)
            real_len = int(len(data)/2)
            if theory_len == real_len:
                data_list = [int(data[i:i+2], 16) for i in range(0,len(data)) if i%2==0]
                s = hex(sum(data_list) % 256).replace('0x','').zfill(2)[-2:]
                _sum = recv[-2:]
                if s == _sum:
                    addr    = data[0:8]
                    _type   = data[8:10]
                    cmd     = data[10:12]
                    content = data[12:]
                    if addr == localhost_cid:
                        if cmd == "B3":
                            print("得到终端回应,关闭请求,开始发送心跳包")
                            self.connect_alive = False
                            self.heart_alive = True
                            self.thread_heart = Thread(target=self.heartThread)
                            self.thread_heart.setDaemon(True)
                            self.thread_heart.start()
                        if cmd == NODE_OPEN_TAP:
                            print("收到打开阀门命令")
                            self.cmd_send(
                                target=localhost_cid,
                                _type=TYPE,
                                cmd=NODE_TAP_IS_OPEN,
                            )
                        if cmd == NODE_CLOSE_TAP:
                            print("收到关闭阀门命令")
                            self.cmd_send(
                                target=localhost_cid,
                                _type=TYPE,
                                cmd=NODE_TAP_IS_CLOSE,
                            )
                    print("接收到正确命令: %s"%recv)
        return False

    def cmd_send(self, lora_addr='', target='',local='',_type='',cmd='',content=''):
        header = 'AA'
        data = target+local+_type+cmd+content
        length = hex(int(len(data)/2)).replace('0x','').zfill(2)[-2:]
        if len(data)>0 and len(data) % 2 == 0:
            data_list = [int(data[i:i+2], 16) for i in range(0,len(data)) if i%2==0]
            _sum = hex(sum(data_list) % 256).replace('0x','').zfill(2)[-2:]
            cmd = lora_addr+header+length+data+_sum
            try:
                self.write(cmd.upper(), 'hex')
            except Exception as e:
                print(e)

    def recv_deal(self):
        '''串口处理命令，监测校验和是否正确'''
        self.recv = self.recv
        start = self.recv.find("AA")
        startwith = self.recv.startswith('AA')
        def find_last(string, s):
            last_position = -1
            while True:
                position = string.find(s, last_position + 1)
                if position == -1:
                    return last_position
                last_position = position
        start_num = len(re.compile('.*?(AA).*?').findall(self.recv))
        if self.recv.startswith('AA'):
            for i in re.compile('.*?(AA).*?').findall(self.recv):
                if not len(self.recv)>4:
                    return
                else:
                    theory_len = int(self.recv[2:4], 16)
                    real_len = len(self.recv) / 2 - 3
                    if not theory_len <= real_len:
                        return
                    else:
                        end = (theory_len+3)*2
                        cmd = self.recv[start:end]
                        self.recv = self.recv[end:]
                        s = 0
                        cmd_sum = cmd[-2:]
                        cmd_data = cmd[4:-2]
                        for i in range(0, len(cmd_data)):
                            if i % 2 == 0:
                                s = s + int(cmd_data[i:i + 2], 16)
                        s = hex(s).upper()[-2:]
                        if s == cmd_sum :
                            print('校验和正确')
                            self.cmd_deal(cmd_data)
                        else:
                            self.recv = ''
                            print('校验错误！！！！！！！！')
                            print('正确：', s, '错误:', cmd_sum)
        else:
            self.recv = self.recv[self.recv.find('AA'):]

    def str_to_hex(self, s):
        """字符串转16进制"""
        return ''.join([hex(ord(c)).replace('0x', '').zfill(2) for c in s])

    def write(self, msg, base='str'):
        """串口写入msg,写入需要将字符串转换成bytes"""
        if self.serial_alive:
            if self.serial.isOpen():
                # 十六进制格式字符串转bytes
                # print(type(msg))
                if base == 'str':
                    self.serial.write(msg.encode('utf-8'))
                if base == 'hex':
                    self.serial.write(binascii.unhexlify(msg))
                print("串口发送命令：%s"%msg)
        else:
            print("serial is not open!!!")

    def writeBytes(self, msg):
        """串口写入msg,写入需要将字符串转换成bytes"""
        if self.serial_alive:
            if self.serial.isOpen():
                try:
                    self.serial.write(msg)
                    print("串口发送命令：%s"%msg)
                except Exception as e:
                    print(e)
        else:
            print("serial is not open!!!")

    def sendToUrl(self, msg):
        print("发送消息到网页")
        url = 'http://localhost:5000/serial_recv'
        body = {"msg": msg}
        headers = {'content-type': "application/json"}
        response = requests.post(url, data = json.dumps(body), headers = headers)

if __name__ == '__main__':
    ser = Ser()
    while True:
        time.sleep(1)

